﻿using System;
using System.Collections;
using CI.Interfaces;
using System.Reflection;
using System.Runtime.Serialization;

namespace CI.Organism
{
    /// <summary>
    /// Requirement item that has to be met before the Gene action can be activated
    /// base class
    /// </summary>
    [DataContract]
    public abstract class Requirement : IRequirement
    {
        #region Properties
        /// <summary>Gene's GUID used to uniquely identify it in the requirement </summary>
        [DataMember]
        public Guid Guid { get; set; }

        /// <summary>Organism which we are working with</summary>
        [DataMember]
        public IOrganism Organism { get; set; }

        /// <summary>Comparison Type then checking state, eg equal, less then, greater then</summary>
        [DataMember]
        public ComparisonType ComparisonType { get; set; }

        #endregion

        /// <summary>
        /// Take this parent Requirement and create a new requirement to be put in the new child organism
        /// Mutate the current requirement and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new requirement for child</returns>
        public abstract IRequirement Mutate(double mutationRate, IOrganism childOrganism);

        /// <summary>
        /// To String, show compairson type
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("Comparison Type : {0}", this.ComparisonType);
        }

        /// <summary>
        /// base class save the guid
        /// </summary>
        /// <param name="guid"></param>
        public Requirement(Guid guid)
        {
            #region Argument check
            if (guid == Guid.Empty)
                throw new ArgumentException("guid");
            #endregion

            this.Guid = guid;
        }

        /// <summary>
        /// Create a new requirement
        /// </summary>
        /// <param name="mutationRate">Mutation rate</param>
        /// <param name="organism">childOrganism</param>
        /// <returns>new requirement</returns>
        public static IRequirement CreateNewRequirement(double mutationRate, IOrganism organism)
        {
            #region Argument check
            if (!(organism is IOrganism))
                throw new ArgumentException("Organism");
            #endregion

            // we need to reflect and see what genes are available
            ArrayList requirements = Globals.GetConcreteClasses(typeof(IRequirement));

            // save its type
            Type classType;

            var noIOSynapses = organism.GetIOManifest().Count == 0;

            // get a new type
            do
            {
                classType = (Type)requirements[Mutation.CollectionSelect(requirements)];
            }
            // if it has no brain dont let it get a neural type requirment
            // todo : come up with a neural type for the requirements to make this more generic
            while (noIOSynapses && (classType == typeof(IOSynapseValue)));

            // We are anticipating a constructor that takes 2 parameters,
            var conTypes = new Type[3];
            conTypes[0] = typeof(IOrganism);
            conTypes[1] = typeof(ComparisonType);
            conTypes[2] = typeof(Guid);
            // Get the constructor that adheres to our parameters.
            ConstructorInfo constructor = classType.GetConstructor(conTypes);
            // Define the parameters to pass into our constructor when we call it.
            var constructorParams = new object[3];
            constructorParams[0] = organism;
            constructorParams[1] = ((ComparisonType)(new System.Random().Next(1, Enum.GetValues(typeof(ComparisonType)).Length)));
            constructorParams[2] = Guid.NewGuid();
            SimEnv.Instance.TraceLogger.LogDebug(string.Format("Invoking constructor of type {0}", classType));

            // Invoke the constructor dynamically, getting an instance of the class.            
            var requirement = constructor.Invoke(constructorParams) as IRequirement;

            // mutate the default gene and add it                
            requirement = requirement.Mutate(mutationRate, organism);

            return requirement;
        }

        /// <summary>
        /// Does the requirement type meet the requirements
        /// </summary>
        /// <returns>True if it meets the requirements else false</returns>
        public abstract bool State();

        /// <summary>
        /// do a equals comparison of one requirement type to another only comparing the value types, no refference comparisons
        /// </summary>
        /// <param name="requirement">requirement to compare against</param>
        /// <returns>true if equal value types</returns>
        public virtual bool EqualsValueTypes(IRequirement requirement)
        {
            // if nothing to compare against
            if (requirement == null)
                return false;

            // check the values
            if (ComparisonType != requirement.ComparisonType)
                return false;
            if (Guid != requirement.Guid)
                return false;

            return true;
        }

    }
}
